/*-------------------------------------------------------------------------
 *
 * like_match.c
 *	  LIKE模式匹配内部代码。
 *
 * 此文件被like.c包含四次，以提供匹配代码
 * （1）单字节编码，（2）UTF8，（3）其他多字节编码，
 * 和（4）单字节编码中的不区分大小写的匹配。
 * （UTF8是一个特殊情况，因为我们可以使用比一般
 * 多字节编码更高效的NextChar版本。）
 *
 * 在包含之前，我们需要定义以下宏：
 *
 * NextChar
 * MatchText - 所需的函数名称
 * do_like_escape - 所需的函数名称 - 需要CHAREQ和CopyAdvChar
 * MATCH_LOWER - 为情况（4）定义，以指定1字节字符的大小写折叠
 *
 * Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	src/backend/utils/adt/like_match.c
 *
 *-------------------------------------------------------------------------
 */

/* 
 *	最初由 Rich $alz 编写，mirror!rs，1986年11月26日星期三 19:03:17 EST。
 *	Rich $alz 现在是 <rsalz@bbn.com>。
 *	特别感谢 Lars Mathiesen <thorinn@diku.dk> 提供
 *	LIKE_ABORT 代码。
 *
 *	这段代码是我毫无羞耻地从 “pql” 代码中盗取的，并稍作修改 :)
 *
 *	所有对 “star” 一词的引用都被替换为 “percent”
 *	所有对 “wild” 一词的引用都被替换为 “like”
 *
 *	所有漂亮的 shell RE 匹配部分都被简单的 "_" 和 "%" 所替代
 *
 *	因为我没有 SQL 标准的复本，所以不确定是否
 *	保留反斜杠转义字符处理。
 *
 *	Keith Parks。 <keith@mtcc.demon.co.uk>
 *
 *	SQL 允许你通过说
 *	LIKE <pattern> ESCAPE <escape character> 来指定转义字符。我们是一个小操作
 *	所以我们强制您使用 '\'。 - ay 7/95
 *
 *	现在我们有了 like_escape() 函数，可以将带有
 *	任何指定转义字符（或根本没有）转换为内部
 *	默认转义字符，仍然是 '\'。 - tgl 9/2000
 *
 * 该代码已重写，以避免需要空终止字符串，
 * 这反过来使我们可以省略一些 memcpy() 操作。
 * 这段代码应该更快，占用更少的内存，但不保证...
 * - thomas 2000-08-06
 */


/* --------------------
 *	匹配文本和模式，返回 LIKE_TRUE、LIKE_FALSE 或 LIKE_ABORT。
 *
 *	LIKE_TRUE：它们匹配
 *	LIKE_FALSE：它们不匹配
 *	LIKE_ABORT：不仅不匹配，而且文本太短。
 *
 * 如果返回 LIKE_ABORT，则文本的任何后缀也无法匹配
 * 模式，因此上层 % 扫描现在可以停止扫描。
 * -------------------- */

#ifdef MATCH_LOWER
#define GETCHAR(t) MATCH_LOWER(t)
#else
#define GETCHAR(t) (t)
#endif

static int MatchText(const char *fc_t, int fc_tlen, const char *fc_p, int fc_plen,
		  pg_locale_t fc_locale, bool fc_locale_is_c)
{
	/* 快速路径以匹配所有模式 */
	if (fc_plen == 1 && *fc_p == '%')
		return LIKE_TRUE;

	/* 由于此函数是递归的，它可能会导致栈溢出 */
	check_stack_depth();

	/* 
	 * 在这个循环中，当匹配通配符时，我们按字符前进（因此递归进入该函数时，我们
	 * 是正确的字符同步）。在其他情况下，可以安全地按字节前进，因为文本和模式将
	 * 同步进行。这允许我们在字节基础上执行所有文本和模式之间的比较，甚至对于
	 * 多字节编码。
	 */
	while (fc_tlen > 0 && fc_plen > 0)
	{
		if (*fc_p == '\\')
		{
			/* 下一个模式字节必须逐字匹配，无论它是什么 */
			NextByte(fc_p, fc_plen);
			/* ... 并且根据 SQL 标准更好有一个 */
			if (fc_plen <= 0)
#ifdef FDD//cppcheck
			{
#endif
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
						 errmsg("LIKE pattern must not end with escape character")));
#ifdef FDD//cppcheck
				return LIKE_FALSE;
			}
#endif
			if (GETCHAR(*fc_p) != GETCHAR(*fc_t))
				return LIKE_FALSE;
		}
		else if (*fc_p == '%')
		{
			char		fc_firstpat;

			/* 
			 * % 处理实际上是在搜索一个文本位置
			 * 其余文本与剩余模式匹配，使用递归调用检查每个潜在匹配。
			 *
			 * 如果有通配符紧随其后，我们可以先跳过它们，使用任何 N 个 _ 和
			 * 一个或多个 % 相当于 N 个 _ 和一个 % 的想法（即，它将
			 * 匹配至少 N 个文本字符的任何序列）。通过这种方式，
			 * 我们将始终使用以字面字符开头的模式片段来运行递归搜索循环，从而
			 * 不会递归到更多的地方。
			 */
			NextByte(fc_p, fc_plen);

			while (fc_plen > 0)
			{
				if (*fc_p == '%')
					NextByte(fc_p, fc_plen);
				else if (*fc_p == '_')
				{
					/* 如果剩余文本不足以匹配模式，则中止 */
					if (fc_tlen <= 0)
						return LIKE_ABORT;
					NextChar(fc_t, fc_tlen);
					NextByte(fc_p, fc_plen);
				}
				else
					break;		/* 达到了一个非通配符模式字符 */
			}

			/* 
			 * 如果我们处于模式的末尾，匹配：我们有一个尾随的 % 匹配
			 * 任何剩余文本字符串。
			 */
			if (fc_plen <= 0)
				return LIKE_TRUE;

			/* 
			 * 否则，扫描一个文本位置，在该位置我们可以匹配
			 * 其余模式。第一个剩余的模式字符被知道是一个常规或转义字面字符，
			 * 因此我们可以将第一个模式字节与每个文本字节进行比较，以避免递归
			 * 超出必须的范围。这个事实还保证我们不
			 * 必须考虑匹配文本末尾的零长度子串。
			 */
			if (*fc_p == '\\')
			{
				if (fc_plen < 2)
#ifdef FDD//cppcheck
				{
#endif
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
							 errmsg("LIKE pattern must not end with escape character")));
#ifdef FDD//cppcheck
					return LIKE_ABORT;
				}
#endif
				fc_firstpat = GETCHAR(fc_p[1]);
			}
			else
				fc_firstpat = GETCHAR(*fc_p);

			while (fc_tlen > 0)
			{
				if (GETCHAR(*fc_t) == fc_firstpat)
				{
					int			fc_matched = MatchText(fc_t, fc_tlen, fc_p, fc_plen,
													fc_locale, fc_locale_is_c);

					if (fc_matched != LIKE_FALSE)
						return fc_matched; /* TRUE 或 ABORT */
				}

				NextChar(fc_t, fc_tlen);
			}

			/* 
			 * 结尾文本没有匹配，因此试图在稍后的地方
			 * 开始匹配此模式是没有意义的。
			 */
			return LIKE_ABORT;
		}
		else if (*fc_p == '_')
		{
			/* _ 匹配任何单个字符，我们知道有一个 */
			NextChar(fc_t, fc_tlen);
			NextByte(fc_p, fc_plen);
			continue;
		}
		else if (GETCHAR(*fc_p) != GETCHAR(*fc_t))
		{
			/* 非通配符模式字符未能与文本字符匹配 */
			return LIKE_FALSE;
		}

		
/*
		 * 模式和文本匹配，所以向前推进。
		 *
		 * 在这里使用 NextByte 而不是 NextChar 是安全的，即使对于
		 * 多字节字符集，因为我们并没有紧跟在通配符字符之后。如果我们处于
		 * 多字节字符的中间，我们必须已经从文本和模式中至少匹配了字符的
		 * 一个字节；因此我们无法在字符边界上失去同步。而且我们知道，
		 * 没有后端合法的编码允许 ASCII 字符（如 '%'）出现在字符的非首字节
		 * 位置，因此我们不会错误地检测到新的通配符。
		 */
		NextByte(fc_t, fc_tlen);
		NextByte(fc_p, fc_plen);
	}

	if (fc_tlen > 0)
		return LIKE_FALSE;		/* 模式结束，但文本未结束 */

	/*
	 * 文本结束，但模式可能未结束。仅当剩余模式可以匹配一个
	 * 长度为零的字符串时才匹配，即它可以是零个或多个 % 的组合。
	 */
	while (fc_plen > 0 && *fc_p == '%')
		NextByte(fc_p, fc_plen);
	if (fc_plen <= 0)
		return LIKE_TRUE;

	/*
	 * 文本结束但没有匹配，因此在后面的地方尝试开始匹配
	 * 此模式是没有意义的。
	 */
	return LIKE_ABORT;
}								/* MatchText() */

/* 
 * like_escape() --- 给定一个模式和一个 ESCAPE 字符串，
 * 将模式转换为使用 Postgres 的标准反斜杠转义约定。
 */
#ifdef do_like_escape

static text * do_like_escape(text *fc_pat, text *fc_esc)
{
	text	   *fc_result;
	char	   *fc_p,
			   *fc_e,
			   *fc_r;
	int			fc_plen,
				fc_elen;
	bool		fc_afterescape;

	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);
	fc_e = VARDATA_ANY(fc_esc);
	fc_elen = VARSIZE_ANY_EXHDR(fc_esc);

	/*
	 * 最坏情况的模式增长为 2 倍---不太可能，但几乎没有必要
	 * 更准确地计算大小。
	 */
	fc_result = (text *) palloc(fc_plen * 2 + VARHDRSZ);
	fc_r = VARDATA(fc_result);

	if (fc_elen == 0)
	{
		/*
		 * 不需要转义字符。在模式中将任何反斜杠加倍，使它们
		 * 像普通字符一样工作。
		 */
		while (fc_plen > 0)
		{
			if (*fc_p == '\\')
				*fc_r++ = '\\';
			CopyAdvChar(fc_r, fc_p, fc_plen);
		}
	}
	else
	{
		/*
		 * 指定的转义字符必须只有一个字符。
		 */
		NextChar(fc_e, fc_elen);
		if (fc_elen != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
					 errmsg("invalid escape string"),
					 errhint("Escape string must be empty or one character.")));

		fc_e = VARDATA_ANY(fc_esc);

		/*
		 * 如果指定的转义字符是'\'，就按原样复制模式。
		 */
		if (*fc_e == '\\')
		{
			memcpy(fc_result, fc_pat, VARSIZE_ANY(fc_pat));
			return fc_result;
		}

		/*
		 * 否则，将指定的转义字符的出现转换为'\'，并将'\'的出现翻倍——除非它们紧接在转义字符之后！
		 */
		fc_afterescape = false;
		while (fc_plen > 0)
		{
			if (CHAREQ(fc_p, fc_e) && !fc_afterescape)
			{
				*fc_r++ = '\\';
				NextChar(fc_p, fc_plen);
				fc_afterescape = true;
			}
			else if (*fc_p == '\\')
			{
				*fc_r++ = '\\';
				if (!fc_afterescape)
					*fc_r++ = '\\';
				NextChar(fc_p, fc_plen);
				fc_afterescape = false;
			}
			else
			{
				CopyAdvChar(fc_r, fc_p, fc_plen);
				fc_afterescape = false;
			}
		}
	}

	SET_VARSIZE(fc_result, fc_r - ((char *) fc_result));

	return fc_result;
}
#endif							/* do_like_escape */

#ifdef CHAREQ
#undef CHAREQ
#endif

#undef NextChar
#undef CopyAdvChar
#undef MatchText

#ifdef do_like_escape
#undef do_like_escape
#endif

#undef GETCHAR

#ifdef MATCH_LOWER
#undef MATCH_LOWER

#endif
